package rabbit.flt.core;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import rabbit.flt.common.AbstractConfigFactory;
import rabbit.flt.common.AgentConfig;
import rabbit.flt.common.log.AgentLoggerFactory;
import rabbit.flt.common.log.Logger;
import rabbit.flt.common.utils.ResourceUtils;
import rabbit.flt.common.utils.StringUtils;
import rabbit.flt.core.factory.DefaultConfigFactory;
import rabbit.flt.core.loader.DefaultPluginClassLoader;
import rabbit.flt.core.transformer.ClassTransformer;
import rabbit.flt.plugins.common.Matcher;
import rabbit.flt.plugins.common.MetricsPlugin;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.List;
import java.util.jar.JarFile;

import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static rabbit.flt.common.AbstractConfigFactory.CLASS_PATH_PREFIX;

/**
 * 代理人口
 */
public class AgentEntry {

    private Logger logger = AgentLoggerFactory.getLogger(getClass());

    private static final AgentEntry inst = new AgentEntry();

    private AgentEntry() {
    }

    public static void premain(String agentConfig, Instrumentation instrumentation) throws IOException, URISyntaxException {
        inst.enhanceByPlugins(agentConfig, instrumentation);
    }

    /**
     * 按插件进行增强
     *
     * @param agentConfig
     * @param instrumentation
     * @throws FileNotFoundException
     */
    private void enhanceByPlugins(String agentConfig, Instrumentation instrumentation) throws IOException, URISyntaxException {
        if (StringUtils.isEmpty(agentConfig)) {
            return;
        }
        AbstractConfigFactory.setFactoryLoader(DefaultConfigFactory::new);

        // 初始化
        PluginClassLoader.setPluginClassLoader(new DefaultPluginClassLoader());

        // 设置配置文件
        AbstractConfigFactory.setAgentConfigFile(agentConfig);

        tryInitFactory(agentConfig);

        // 添加扩展插件类到classpath中去
        addExtensionPluginsToClassPath(instrumentation);

        MetricsPlugin.tryStartingAllPlugins();

        List<Matcher> matchers = Matcher.loadMatchers();
        ElementMatcher.Junction<TypeDescription> classMatcher = null;
        for (Matcher matcher : matchers) {
            if (null == classMatcher) {
                classMatcher = matcher.classMatcher();
            } else {
                logger.info("found matcher: {}", matcher.getClass().getSimpleName());
                classMatcher = classMatcher.or(matcher.classMatcher());
            }
        }
        getBasicAgentBuilder().type(classMatcher).transform(new ClassTransformer(matchers))
                .installOn(instrumentation);
    }

    public static AgentBuilder getBasicAgentBuilder() {
        ElementMatcher.Junction<NamedElement> ignoreMatcher = nameStartsWith("org.slf4j.")
                .or(nameStartsWith("com.intellij."))
                .or(nameStartsWith("java."))
                .or(nameStartsWith("javax."));
        AgentConfig config = AbstractConfigFactory.getConfig();
        if (null != config) {
            String ignorePackages = config.getIgnorePackages();
            if (!StringUtils.isEmpty(ignorePackages)) {
                for (String pkg : ignorePackages.trim().split(",")) {
                    ignoreMatcher = ignoreMatcher.or(nameStartsWith(pkg.trim()));
                    inst.logger.info("ignore package: {}", pkg);
                }
            }
            String ignoreClasses = config.getIgnoreClasses();
            if (!StringUtils.isEmpty(ignoreClasses)) {
                for (String clz : ignoreClasses.trim().split(",")) {
                    ignoreMatcher = ignoreMatcher.or(named(clz.trim()));
                    inst.logger.info("ignore class: {}", clz);
                }
            }
        }
        return new AgentBuilder.Default().ignore(ignoreMatcher);
    }

    /**
     * 加载扩展插件
     */
    private void addExtensionPluginsToClassPath(Instrumentation instrumentation) throws IOException, URISyntaxException {
        String dirName = new AgentConfig().getExtensionPluginsDirectories();
        AgentConfig config = AbstractConfigFactory.getConfig();
        if (null != config) {
            dirName = config.getExtensionPluginsDirectories();
        }
        String parent = new File(PluginClassLoader.getPluginClassLoader().getAgentJarFilePath()).getParent();
        String pluginPath = Paths.get(parent, dirName).toString();
        File dir = new File(pluginPath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        for (File file : dir.listFiles()) {
            if (!file.isFile() || !file.getName().endsWith(".jar")) {
                continue;
            }
            logger.info("found extension plugin: {}", file.getName());
            instrumentation.appendToSystemClassLoaderSearch(new JarFile(file.getAbsoluteFile()));
        }
    }

    /**
     * 尝试初始化配置工厂
     *
     * @param agentConfig
     * @throws FileNotFoundException
     */
    private void tryInitFactory(String agentConfig) throws FileNotFoundException {
        if (agentConfig.toLowerCase().startsWith(CLASS_PATH_PREFIX)) {
            return;
        }
        File file = new File(agentConfig);
        FileInputStream stream = new FileInputStream(file);
        DefaultConfigFactory factory = (DefaultConfigFactory) AbstractConfigFactory.getFactory();
        factory.setConfig(factory.loadConfig(stream));
        ResourceUtils.close(stream);
    }
}
